Skip to content

Add configurable character limits and feature toggles for polls#6435

Open
ryanhurststrava wants to merge 4 commits into
GetStream:v6from
ryanhurststrava:feature/poll_character_limits_v2
Open

Add configurable character limits and feature toggles for polls#6435
ryanhurststrava wants to merge 4 commits into
GetStream:v6from
ryanhurststrava:feature/poll_character_limits_v2

Conversation

@ryanhurststrava
Copy link
Copy Markdown

@ryanhurststrava ryanhurststrava commented May 11, 2026

🎯 Goal

Introduce comprehensive configuration for poll creation features, allowing developers to control which features are available to users, set default values, and enforce character limits on questions and options. This provides flexibility for different use cases where certain poll features may need to be restricted, pre-configured, or character-limited based on UI constraints, business rules, or localization requirements. This loosely follows the iOS implementation.

🛠 Implementation

New Files Created

PollsConfig.kt

  • New PollsConfig data class for configuring all poll creation features
  • New PollsEntryConfig data class for individual feature configuration (configurable flag + default value)
  • Configurable features:
    • multipleVotes - Allow users to select multiple options
    • anonymousPoll - Hide voter identities
    • suggestAnOption - Allow users to add their own options
    • addComments - Allow users to add comments to polls
  • Character limits:
    • questionTextLimit - Optional character limit for poll questions
    • optionTextLimit - Optional character limit for poll options
  • Both classes implement Parcelable for passing via Bundle arguments
  • Includes Default and NotConfigurable presets

Modified Files

ChatUI.kt

  • Added pollsConfig property to provide global default configuration
  • Defaults to PollsConfig.Default (all features configurable and disabled by default)

CreatePollDialogFragment.kt

  • Updated newInstance() to accept optional PollsConfig parameter
  • Falls back to ChatUI.pollsConfig when not provided
  • New configurePollFeatures() method to show/hide UI elements based on config
  • Applies character limits using InputFilter.LengthFilter
  • Wires up "add a comment" feature that was previously in UI but not functional
  • Sets initial switch states based on config default values

CreatePollViewModel.kt

  • Added allowAnswers field and setAllowAnswers() method
  • Passes allowAnswers to the poll configuration when creating polls

OptionsAdapter.kt

  • Added optionTextLimit parameter to constructor
  • Applies InputFilter.LengthFilter to option EditText fields when limit is specified
  • Maintains backward compatibility with overloaded constructor

Key Design Decisions

  • Backward Compatibility: All features remain configurable by default; existing code continues to work without changes
  • Two-Level Configuration: Features can be controlled via global ChatUI.pollsConfig or per-dialog via newInstance()
  • Visibility Control: When a feature is not configurable, its UI elements are completely hidden
  • Default Values: Features can be pre-configured with default on/off states
  • Optional Limits: Character limits default to null (unlimited) unless explicitly specified

Usage Examples

// Global configuration
ChatUI.pollsConfig = PollsConfig(
    multipleVotes = PollsEntryConfig(configurable = false, defaultValue = false),
    anonymousPoll = PollsEntryConfig(configurable = true, defaultValue = true),
    questionTextLimit = 200,
    optionTextLimit = 100
)

// Per-dialog configuration
CreatePollDialogFragment.newInstance(
    createPollDialogListener = myListener,
    pollsConfig = PollsConfig(
        suggestAnOption = PollsEntryConfig.NotConfigurable,
        addComments = PollsEntryConfig(configurable = true, defaultValue = false),
        questionTextLimit = 150,
        optionTextLimit = 75
    )
)

📱 UI Changes

  • Poll features can now be hidden when not configurable
  • Features can be pre-enabled with default values
  • Character limits are enforced at the input level using InputFilter
  • "Add a comment" switch is now functional

✅ Testing

Manual Testing Steps

  1. Test default behavior - all features configurable, none enabled by default
  2. Hide features - set configurable = false, verify UI elements are hidden
  3. Set default values - verify switches initialize to correct state
  4. Test character limits on questions and options
  5. Verify "add a comment" feature now works correctly
  6. Test global ChatUI.pollsConfig vs per-dialog config
  7. Verify backward compatibility with existing newInstance() calls

Expected Behavior

  • Non-configurable features are hidden from the UI
  • Configurable features display with correct default switch states
  • Character limits prevent input beyond specified length
  • Configuration can be set globally or per-dialog
  • Existing code without config works unchanged

Contributor Checklist

  • I have signed the Stream CLA (required)
  • I have assigned a reviewer (code owner)
  • This PR targets the develop branch
  • I have added unit tests for new code
  • I have updated documentation (KDocs included in code)

Reviewer Checklist

  • UI Components sample app has been tested
  • Compose sample app has been tested (N/A - UI Components only)
  • Visuals are correct
  • Feature is validated
  • KDocs have been reviewed
  • SDK size impact has been reviewed from CI logs

🎉 GIF

1778192411.mp4
Screen_recording_20260511_173314.webm

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced comprehensive poll configuration system enabling control over which poll creation features are available to users
    • Added configurable text limits for poll questions and options
    • Made poll settings (multiple votes, anonymous polls, suggest-an-option, add comments) individually configurable with customizable default values
    • Added option to control whether poll responses can include answers

Review Change Stack

Introduces PollsConfig to control poll feature availability and enforce character limits on questions and options. Poll features (multiple votes, anonymous voting, suggest options, add comments) can now be
hidden or preset with default values through ChatUI.pollsConfig or passed directly to CreatePollDialogFragment.
@ryanhurststrava ryanhurststrava marked this pull request as ready for review May 11, 2026 21:49
@ryanhurststrava ryanhurststrava requested a review from a team as a code owner May 11, 2026 21:49
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Walkthrough

This PR introduces a centralized poll configuration system allowing apps to control which poll creation features are user-configurable, their default states, and text input limits. New PollsConfig and PollsEntryConfig parcelable models are exposed globally via ChatUI.pollsConfig, read by CreatePollDialogFragment, and applied to UI visibility, feature defaults, view model state, and text constraints.

Changes

Poll Configuration Feature

Layer / File(s) Summary
Configuration Model
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt
PollsEntryConfig parcelize data class with configurable and defaultValue booleans; PollsConfig parcelize data class aggregating multiple feature entries and optional text limits; both include Default companion presets.
Global Configuration
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt
ChatUI.pollsConfig static property initialized to PollsConfig.Default; import for PollsConfig type.
Dialog Factory & Config Read
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt
newInstance(createPollDialogListener, pollsConfig: PollsConfig? = null) factory method with @JvmOverloads; stores config in fragment arguments under ARG_POLLS_CONFIG; defaults to ChatUI.pollsConfig if not provided.
Feature UI Wiring
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt
configurePollFeatures() helper reads pollsConfig and drives feature switch visibility and checked states for multiple votes, anonymous poll, suggest-an-option, and add-comments based on configurable and defaultValue flags; wires add-comment toggle to setAllowAnswers().
View Model State
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollViewModel.kt
Adds allowAnswers private state property; new public setAllowAnswers(Boolean) method; pollConfig construction includes allowAnswers value.
Option Text Input Limiting
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt
OptionsAdapter constructor updated to accept optional optionTextLimit parameter with public overload for backward compatibility; OptionViewHolder applies InputFilter.LengthFilter(optionTextLimit) to option EditText when limit is non-null; onCreateViewHolder passes limit to view holder.

Poem

🐰 A polls config blooms, so neat and tidy,
Features configurable, defaults stringent,
Text limits guard each option's length with pride,
From ChatUI down to each input field descent!

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding configurable character limits and feature toggles for the poll creation system.
Description check ✅ Passed The description is comprehensive and well-structured, covering goal, implementation details, design decisions, usage examples, testing steps, and includes a GIF demonstration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt (1)

240-240: ⚡ Quick win

Remove unnecessary cast.

The explicit cast to android.os.Parcelable is redundant since PollsConfig already implements Parcelable (defined at line 70 in PollsConfig.kt). Kotlin's type inference handles this automatically.

Simplified code
-                    pollsConfig?.let { config -> putParcelable(ARG_POLLS_CONFIG, config as android.os.Parcelable) }
+                    pollsConfig?.let { putParcelable(ARG_POLLS_CONFIG, it) }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt`
at line 240, Remove the redundant explicit cast in the putParcelable call: where
CreatePollDialogFragment stores pollsConfig with putParcelable(ARG_POLLS_CONFIG,
config as android.os.Parcelable), change it to pass pollsConfig (config)
directly since PollsConfig already implements Parcelable; update the call in the
block that references pollsConfig and ARG_POLLS_CONFIG to remove "as
android.os.Parcelable".
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt (1)

67-67: ⚡ Quick win

Consider renaming addComments to allowAnswers for consistency.

The config property is named addComments, but the underlying model field in CreatePollViewModel (line 49) and PollConfig (line 106 in CreatePollViewModel.kt) is named allowAnswers. This naming mismatch may confuse developers trying to understand how the config maps to the actual poll behavior.

Suggest renaming to maintain consistent terminology across the configuration and implementation layers.

Proposed fix
-    val addComments: PollsEntryConfig = PollsEntryConfig.Default,
+    val allowAnswers: PollsEntryConfig = PollsEntryConfig.Default,

Update corresponding documentation and usage in CreatePollDialogFragment.kt lines 124-129.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt`
at line 67, Rename the PollsConfig property addComments to allowAnswers to match
the field name used in CreatePollViewModel and PollConfig; update the property
declaration in PollsConfig (currently val addComments: PollsEntryConfig =
PollsEntryConfig.Default) to val allowAnswers: PollsEntryConfig =
PollsEntryConfig.Default, then update all usages and bindings (including where
CreatePollDialogFragment populates the config) and any documentation/comments to
use allowAnswers so terminology is consistent across PollsConfig,
CreatePollViewModel, and PollConfig.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt`:
- Around line 30-40: The public API changed because the primary constructor
parameter order for OptionsAdapter now places optionTextLimit first, breaking
existing positional calls; restore a backward-compatible single-argument
constructor by adding a public constructor matching the old signature
(constructor(onOptionChange: (id: Int, text: String) -> Unit)) that delegates to
the new primary (this(null, onOptionChange)), mark it `@Deprecated` with a message
pointing to the new constructor/parameter and add `@JvmOverloads/`@JvmName if
needed for Java callers so existing binaries still resolve the single-arg
constructor while signaling callers to move to the new API.

---

Nitpick comments:
In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt`:
- Line 240: Remove the redundant explicit cast in the putParcelable call: where
CreatePollDialogFragment stores pollsConfig with putParcelable(ARG_POLLS_CONFIG,
config as android.os.Parcelable), change it to pass pollsConfig (config)
directly since PollsConfig already implements Parcelable; update the call in the
block that references pollsConfig and ARG_POLLS_CONFIG to remove "as
android.os.Parcelable".

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt`:
- Line 67: Rename the PollsConfig property addComments to allowAnswers to match
the field name used in CreatePollViewModel and PollConfig; update the property
declaration in PollsConfig (currently val addComments: PollsEntryConfig =
PollsEntryConfig.Default) to val allowAnswers: PollsEntryConfig =
PollsEntryConfig.Default, then update all usages and bindings (including where
CreatePollDialogFragment populates the config) and any documentation/comments to
use allowAnswers so terminology is consistent across PollsConfig,
CreatePollViewModel, and PollConfig.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ab5b5db-0f77-4ecc-a22d-aa08094ec362

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad0cda and 6116f9c.

📒 Files selected for processing (5)
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollViewModel.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt

Comment on lines +99 to +102
createPollViewModel.setAllowMultipleVotes(pollsConfig.multipleVotes.defaultValue)
binding.multipleAnswersLabel.isVisible = pollsConfig.multipleVotes.configurable
binding.multipleAnswersSwitch.isVisible = pollsConfig.multipleVotes.configurable
if (pollsConfig.multipleVotes.configurable) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're introducing a new edge case here.

If we have multipleVotes.defaultValue = true + multipleVotes.configurable = false, then we end up in a state where:

  • In the VM, allowMultipleVotes = true since we set it here
  • multipleAnswersCount is hidden because of configurable = false
  • sendMenuItem is disabled because of the above (due to this logic) so the user can never create the poll

In Compose v7 we changed the behavior to allow multipleVotes = true + maxVotes = null which means unlimited votes, so maybe we could do something like that? It's a behavior change, though.

Another option is to disallow the problematic combo, e.g. we could add a check(multipleVotes.configurable == true || multipleVotes.defaultValue == false) call in PollsConfig's constructor.

cc @andremion @VelikovPetar wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am more in favour of disallowing the problematic combination - I wouldn't want to change the default behaviour in a minor release. But once we port this addition to V7, I would say to align it with the compose implementation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more explicit at what you'd like to see here?

another option would be to remove the multiple votes configuration from this pr. I included it for completeness, but it is not a feature my team needs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also leave it included for completeness. Basically the suggestion is to put this inside PollsConfig constructor:

init {
    require(multipleVotes.configurable || !multipleVotes.defaultValue) {
        "Invalid PollsConfig: multipleVotes cannot have defaultValue=true while " +
            "configurable=false as the user would be unable to set maxVotesAllowed."
    }
}

Comment on lines +160 to +162
binding.addACommentLabelSwitch.setOnCheckedChangeListener { _, isChecked ->
createPollViewModel.setAllowAnswers(isChecked)
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this, but it is more or less unrelated to this pr. I think the current functionality(before this pr) is that this switch doesn't do anything. I wasn't able to get this to work in my testing with the changes in this pr either though, so I'm curious what your thoughts are.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It definitely looks like we had a bug both on the creation side and the consuming side, since we never read "allow answers" when displaying poll messages. We'll check that on our side, thanks for fixing the problem at creation time!

@ryanhurststrava ryanhurststrava force-pushed the feature/poll_character_limits_v2 branch from c698547 to a20a5d8 Compare May 13, 2026 19:22
@gpunto gpunto added the pr:improvement Improvement label May 14, 2026
@gpunto
Copy link
Copy Markdown
Contributor

gpunto commented May 14, 2026

We have some failing static checks:

  • Detekt complaining about a couple of long lines -> you can see the warnings with ./gradlew detekt
  • Api check -> you can regenerate the api file with ./gradlew apiDump
  • Spotless -> you can reformat the code with ./gradlew spotlessApply

Could you fix them? 🙏

…y breaking the long comment line

  2. Spotless - Applied formatting fixes (added license header to PollFeatureConfig.kt)
  3. API Check - Regenerated the API dump file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants